package com.rs2hd.net;

import org.apache.mina.common.IoSession;

/**
 * Immutable packet object.
 *
 * @author Graham
 */
public final class Packet {

    public static enum Size {
        FIXED, VAR_BYTE, VAR_SHORT
    }

    ;

    /**
     * The associated IO session
     */
    private IoSession session;
    /**
     * The ID of the packet
     */
    private int pID;
    /**
     * The length of the payload
     */
    private int pLength;
    /**
     * The payload
     */
    private byte[] pData;
    /**
     * The current index into the payload buffer for reading
     */
    private int caret = 0;
    /**
     * Whether this packet is without the standard packet header
     */
    private boolean bare;
    private Size size = Size.FIXED;

    public Packet(IoSession session, int pID, byte[] pData, boolean bare, Size s) {
        this.session = session;
        this.pID = pID;
        this.pData = pData;
        this.pLength = pData.length;
        this.bare = bare;
        this.size = s;
    }
    

    /**
     * Creates a new packet with the specified parameters.
     *
     * @param session The session to associate with the packet
     * @param pID     The ID of the packet
     * @param pData   The payload of the packet
     * @param bare    Whether this packet is bare, which means that it does
     *                not include the standard packet header
     */
    public Packet(IoSession session, int pID, byte[] pData, boolean bare) {
        this(session, pID, pData, bare, Size.FIXED);
    }

    /**
     * Creates a new packet with the specified parameters. The packet
     * is considered not to be a bare packet.
     *
     * @param session The session to associate with the packet
     * @param pID     The ID of the packet
     * @param pData   The payload the packet
     */
    public Packet(IoSession session, int pID, byte[] pData) {
        this(session, pID, pData, false);
    }

    /**
     * Returns the IO session associated with the packet, if any.
     *
     * @return The <code>IoSession</code> object, or <code>null</code>
     *         if none.
     */
    public IoSession getSession() {
        return session;
    }

    /**
     * Checks if this packet is considered to be a bare packet, which
     * means that it does not include the standard packet header (ID
     * and length values).
     *
     * @return Whether this packet is a bare packet
     */
    public boolean isBare() {
        return bare;
    }

    public Size getSize() {
        return size;
    }

    /**
     * Returns the packet ID.
     *
     * @return The packet ID
     */
    public int getId() {
        return pID;
    }

    /**
     * Returns the length of the payload of this packet.
     *
     * @return The length of the packet's payload
     */
    public int getLength() {
        return pLength;
    }

    /**
     * Returns the entire payload data of this packet.
     *
     * @return The payload <code>byte</code> array
     */
    public byte[] getData() {
        return pData;
    }

    /**
     * Returns the remaining payload data of this packet.
     *
     * @return The payload <code>byte</code> array
     */
    public byte[] getRemainingData() {
        byte[] data = new byte[pLength - caret];
        for (int i = 0; i < data.length; i++) {
            data[i] = pData[i + caret];
        }
        caret += data.length;
        return data;

    }

    /**
     * Reads the next <code>byte</code> from the payload.
     *
     * @return A <code>byte</code>
     */
    public byte readByte() {
        return pData[caret++];
    }

    /**
     * Reads the next <code>short</code> from the payload.
     *
     * @return A <code>short</code>
     */
    public short readShort() {
        return (short) ((short) ((pData[caret++] & 0xff) << 8) | (short) (pData[caret++] & 0xff));
    }

    public int readLEShortA() {
        int i = ((pData[caret++] - 128 & 0xff)) + ((pData[caret++] & 0xff) << 8);
        if (i > 32767)
            i -= 0x10000;
        return i;
    }

    public int readLEShort() {
        int i = ((pData[caret++] & 0xff)) + ((pData[caret++] & 0xff) << 8);
        if (i > 32767)
            i -= 0x10000;
        return i;
    }
	
    /**
	 * Gets a 3-byte integer.
	 * @return The 3-byte integer.
	 */
	public int readTriByte() {
		return ((pData[caret++] << 16) & 0xFF) | ((pData[caret++] << 8) & 0xFF) | (pData[caret++] & 0xFF);
	}

    /**
     * Reads the next <code>int</code> from the payload.
     *
     * @return An <code>int</code>
     */
    public int readInt() {
        return ((pData[caret++] & 0xff) << 24)
                | ((pData[caret++] & 0xff) << 16)
                | ((pData[caret++] & 0xff) << 8)
                | (pData[caret++] & 0xff);
    }

    public int readInt2() {
        return ((pData[caret++] & 0xff) << 16)
                | ((pData[caret++] & 0xff) << 24)
                | (pData[caret++] & 0xff)
                | ((pData[caret++] & 0xff) << 8);
    }

    public int readInt1() {
        return ((pData[caret++] & 0xff) << 8)
                | (pData[caret++] & 0xff)
                | ((pData[caret++] & 0xff) << 24)
                | ((pData[caret++] & 0xff) << 16);
    }

    public int readLEInt() {
        return (pData[caret++] & 0xff)
                | ((pData[caret++] & 0xff) << 8)
                | ((pData[caret++] & 0xff) << 16)
                | ((pData[caret++] & 0xff) << 24);
    }

    /**
     * Reads the next <code>long</code> from the payload.
     *
     * @return A <code>long</code>
     */
    public long readLong() {
        return ((long) (pData[caret++] & 0xff) << 56)
                | ((long) (pData[caret++] & 0xff) << 48)
                | ((long) (pData[caret++] & 0xff) << 40)
                | ((long) (pData[caret++] & 0xff) << 32)
                | ((long) (pData[caret++] & 0xff) << 24)
                | ((long) (pData[caret++] & 0xff) << 16)
                | ((long) (pData[caret++] & 0xff) << 8)
                | ((pData[caret++] & 0xff));
    }

    /**
     * Reads the string which is formed by the unread portion
     * of the payload.
     *
     * @return A <code>String</code>
     */
    public String readString() {
        return readString(pLength - caret);
    }

    public String readRS2String() {
        int start = caret;
        while (pData[caret++] != 0) ;
        return new String(pData, start, caret - start - 1);
    }

    public void readBytes(byte[] buf, int off, int len) {
        for (int i = 0; i < len; i++) {
            buf[off + i] = pData[caret++];
        }
    }

    /**
     * Reads a string of the specified length from the payload.
     *
     * @param length The length of the string to be read
     * @return A <code>String</code>
     */
    public String readString(int length) {
        String rv = new String(pData, caret, length);
        caret += length;
        return rv;
    }

    /**
     * Skips the specified number of bytes in the payload.
     *
     * @param x The number of bytes to be skipped
     */
    public void skip(int x) {
        caret += x;
    }


    public int remaining() {
        return pData.length - caret;
    }

    /**
     * Returns this packet in string form.
     *
     * @return A <code>String</code> representing this packet
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[id=" + pID + ",len=" + pLength + ",data=0x");
        for (int x = 0; x < pLength; x++) {
            sb.append(byteToHex(pData[x], true));
        }
        sb.append("]");
        return sb.toString();
    }

    private static String byteToHex(byte b, boolean forceLeadingZero) {
        StringBuilder out = new StringBuilder();
        int ub = b & 0xff;
        if (ub / 16 > 0 || forceLeadingZero)
            out.append(hex[ub / 16]);
        out.append(hex[ub % 16]);
        return out.toString();
    }

    private static final char[] hex = "0123456789ABCDEF".toCharArray();
	public int readByteA() {
        return (byte) (readByte() - 128);
    }
    public int readShortA() {
        caret += 2;
        return ((pData[caret - 2] & 0xFF) << 8) + (pData[caret - 1] - 128 & 0xFF);
    }

    public byte readByteC() {
        return (byte) -readByte();
    }
	  public int readUnsignedShortBigEndian128() {
        return (pData[caret++]  - 128 & 0xff) + ((pData[caret++]  & 0xff) << 8);
    }
    public byte readByteS() {
		return (byte) (128 - readByte());
	}

}
